package handler

import (
	"encoding/base64"
	"fmt"
	"git.multiverse.io/eventkit/common/model"
	"git.multiverse.io/eventkit/common/model/transaction"
	"git.multiverse.io/eventkit/common/util"
	"git.multiverse.io/eventkit/dxc-server/config"
	dxcConstant "git.multiverse.io/eventkit/dxc-server/constant"
	"git.multiverse.io/eventkit/dxc-server/mgmt/metrics"
	"git.multiverse.io/eventkit/dxc-server/repo"
	"git.multiverse.io/eventkit/dxc-server/service"
	"git.multiverse.io/eventkit/kit/constant"
	"git.multiverse.io/eventkit/kit/handler/base"
	"git.multiverse.io/framework/log"
	jsoniter "github.com/json-iterator/go"
	"runtime/debug"
	"strconv"
	"strings"
	"time"
)

var (
	json = jsoniter.ConfigCompatibleWithStandardLibrary
)

const (
	FieldRequestTime             = "requestTime"
	MismatchFieldsErrorFormatter = "Validation failed, %v mismatch"
	TxnBeginRecoverErr           = "TxnBegin Root Transaction Catch failed, err=%++v, Stack: %s"
	TxnBeginDecodeErr            = "TxnBegin Root Transaction Decode Request failed, err=%s"
	TxnBeginExecuteErr           = "TxnBegin Root Transaction Execute failed, err=%s"
	TxnJoinRecoverErr            = "TxnJoin Branch Transaction Catch failed, err=%++v, Stack: %s"
	TxnJoinDecodeErr             = "TxnJoin Branch Transaction Decode Request failed, err=%s"
	TxnJoinExecuteErr            = "TxnJoin Branch Transaction Execute failed, err=%s"
	TxnEndRecoverErr             = "TxnEnd Catch failed, err=%++v, Stack: %s"
	TxnEndDecodeErr              = "TxnEnd Decode Request failed, err=%s"
	TxnEndExecuteErr             = "TxnEnd Execute failed, err=%s"
)

type DxcHandler struct {
	base.Handler
}

func (h *DxcHandler) TxnBegin(reqBody *[]byte) (*model.CommonResponse, error) {
	var resp *model.CommonResponse
	startTime := time.Now()
	hasError := false

	defer func() {
		if err := recover(); err != nil {
			msg := fmt.Sprintf(TxnBeginRecoverErr, err, string(debug.Stack()))
			log.Errorsf(msg)
			resp = model.BuildErrorResponse(msg)
			hasError = true
		}
		// for metrics
		if config.Cfg.Metrics.Enable {
			endTime := time.Now()
			timeCostMicroseconds := endTime.Sub(startTime).Microseconds()
			metrics.DTSRegisterTimeCostAvgAnalyser.IncreaseTotal(float64(timeCostMicroseconds))
			if hasError {
				metrics.DTSRegisterSuccessRateAnalyser.IncreaseCount(false)
			} else {
				metrics.DTSRegisterSuccessRateAnalyser.IncreaseCount(true)
			}
		}
	}()

	// for metrics
	if config.Cfg.Metrics.Enable {
		metrics.DTSRegisterTPSAnalyser.IncreaseCount()
	}

	// parse reqBody
	var err error
	var paramBytes []byte
	request := transaction.RootTxnBeginRequest{}
	clientSDKVersion := h.GetRequestHeaderValWithKeyIgnoreCaseSilence(constant.ClientSDKVersion)
	if strings.EqualFold(constant.ClientSDKVersionV2, clientSDKVersion) {
		paramBytes, err = util.Decode(*reqBody, &request)
	} else {
		clientSDKVersion = constant.ClientSDKVersionV1
		err = json.Unmarshal(*reqBody, &request)
	}
	if err != nil {
		msg := fmt.Sprintf(TxnBeginDecodeErr, err)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		hasError = true
		return resp, nil
	}
	paramData := base64.StdEncoding.EncodeToString(paramBytes) // base64 encode param
	log.Infosf("TxnBegin Root Transaction, request:%++v, paramData:%s", request, paramData)

	// validation
	hasError, misMatchFields := validationParamsForTxnBegin(request)
	if hasError {
		msg := fmt.Sprintf(MismatchFieldsErrorFormatter, misMatchFields)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		hasError = true
		return resp, nil
	}

	// execute request
	repository := repo.RepositoryInstance()
	dxcService := service.NewDefaultService(dxcConstant.SOA, repository, h.Ctx)
	rootXid, err, errCode := dxcService.Begin(request, paramData, clientSDKVersion)
	if err != nil {
		msg := fmt.Sprintf(TxnBeginExecuteErr, err)
		log.Errorsf(msg)
		resp = model.BuildErrorResponseWithErrorCode(errCode, msg)
		hasError = true
		return resp, nil
	}
	result := transaction.RootTxnBeginResponseBody{
		RootXid:      rootXid,
		ResponseTime: util.CurrentTime(),
	}
	resp = model.BuildResponse(result)
	return resp, nil
}

func (h *DxcHandler) TxnJoin(reqBody *[]byte) (*model.CommonResponse, error) {
	var resp *model.CommonResponse
	startTime := time.Now()
	hasError := false

	defer func() {
		if err := recover(); err != nil {
			msg := fmt.Sprintf(TxnJoinRecoverErr, err, string(debug.Stack()))
			log.Errorsf(msg)
			resp = model.BuildErrorResponse(msg)
			hasError = true
		}
		// for metrics
		if config.Cfg.Metrics.Enable {
			endTime := time.Now()
			timeCostMicroseconds := endTime.Sub(startTime).Microseconds()
			metrics.DTSEnlistTimeCostAvgAnalyser.IncreaseTotal(float64(timeCostMicroseconds))
			if hasError {
				metrics.DTSEnlistSuccessRateAnalyser.IncreaseCount(false)
			} else {
				metrics.DTSEnlistSuccessRateAnalyser.IncreaseCount(true)
			}
		}
	}()

	// for metrics
	if config.Cfg.Metrics.Enable {
		metrics.DTSEnlistTPSAnalyser.IncreaseCount()
	}

	// parse request
	var err error
	var paramBytes []byte
	request := transaction.BranchTxnJoinRequest{}
	clientSDKVersion := h.GetRequestHeaderValWithKeyIgnoreCaseSilence(constant.ClientSDKVersion)
	if strings.EqualFold(constant.ClientSDKVersionV2, clientSDKVersion) {
		paramBytes, err = util.Decode(*reqBody, &request)
	} else {
		clientSDKVersion = constant.ClientSDKVersionV1
		err = json.Unmarshal(*reqBody, &request)
	}

	if err != nil {
		msg := fmt.Sprintf(TxnJoinDecodeErr, err)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		hasError = true
		return resp, nil
	}
	paramData := base64.StdEncoding.EncodeToString(paramBytes) // base64 encode param
	log.Infosf("enlist branch transaction, request:%++v, paramData:%s", request, paramData)

	// validation
	hasError, misMatchFields := validationParamsForTxnJoin(request)
	if hasError {
		msg := fmt.Sprintf(MismatchFieldsErrorFormatter, misMatchFields)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		hasError = true
		return resp, nil
	}

	// execute request
	repository := repo.RepositoryInstance()
	dxcService := service.NewDefaultService(dxcConstant.SOA, repository, h.Ctx)
	branchXid, err, errCode := dxcService.Join(request, paramData, clientSDKVersion)
	if err != nil {
		msg := fmt.Sprintf(TxnJoinExecuteErr, err)
		log.Errorsf(msg)
		resp = &model.CommonResponse{
			ErrorCode: errCode,
			ErrorMsg:  msg,
			Data: transaction.BranchTxnJoinResponseBody{
				BranchXid:    branchXid,
				ResponseTime: util.CurrentTime(),
			},
		}
		hasError = true
		return resp, nil
	}
	result := transaction.BranchTxnJoinResponseBody{
		BranchXid:    branchXid,
		ResponseTime: util.CurrentTime(),
	}
	resp = model.BuildResponse(result)
	return resp, nil
}

func (h *DxcHandler) TxnEnd(reqBody *[]byte) (*model.CommonResponse, error) {
	var resp *model.CommonResponse
	startTime := time.Now()
	var hasError bool
	defer func() {
		if err := recover(); err != nil {
			msg := fmt.Sprintf(TxnEndRecoverErr, err, string(debug.Stack()))
			log.Errorsf(msg)
			resp = model.BuildErrorResponse(msg)
			hasError = true
		}
		// for metrics
		if config.Cfg.Metrics.Enable {
			endTime := time.Now()
			timeCostMicroseconds := endTime.Sub(startTime).Microseconds()
			metrics.DTSTryResultReportTimeCostAvgAnalyser.IncreaseTotal(float64(timeCostMicroseconds))
			if hasError {
				metrics.DTSEnlistSuccessRateAnalyser.IncreaseCount(false)
			} else {
				metrics.DTSEnlistSuccessRateAnalyser.IncreaseCount(true)
			}
		}
	}()

	// for metrics
	if config.Cfg.Metrics.Enable {
		metrics.DTSTryResultReportTPSAnalyser.IncreaseCount()
	}

	// parse request
	request := transaction.TxnEndRequest{}
	if err := json.Unmarshal(*reqBody, &request); err != nil {
		msg := fmt.Sprintf(TxnEndDecodeErr, err)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		hasError = true
		return resp, nil
	}

	// validation
	hasError, misMatchFields := validationParamsForTxnEnd(request)
	if hasError {
		msg := fmt.Sprintf(MismatchFieldsErrorFormatter, misMatchFields)
		log.Errorsf(msg)
		resp = model.BuildErrorResponse(msg)
		return resp, nil
	}
	log.Infosf("transaction try result report, request:%++v", request)
	// execute request
	repository := repo.RepositoryInstance()
	dxcService := service.NewDefaultService(dxcConstant.SOA, repository, h.Ctx)
	err, errCode, totalParticipatorNum := dxcService.End(request)
	if err != nil {
		msg := fmt.Sprintf(TxnEndExecuteErr, err)
		log.Errorsf(msg)
		resp = model.BuildErrorResponseWithErrorCode(errCode, msg)
		if 0 != totalParticipatorNum {
			mp := map[string]string{dxcConstant.APM_ATTACH: dxcConstant.APM_ATTACH_PARTICIPATOR_NUM_PREFIX + strconv.Itoa(totalParticipatorNum)}
			h.SetResponseHeader(mp)
		}
		hasError = true
		return resp, nil
	}
	result := transaction.TxnEndResponseBody{
		ResponseTime: util.CurrentTime(),
	}

	if 0 != totalParticipatorNum {
		mp := map[string]string{dxcConstant.APM_ATTACH: dxcConstant.APM_ATTACH_PARTICIPATOR_NUM_PREFIX + strconv.Itoa(totalParticipatorNum)}
		h.SetResponseHeader(mp)
	}
	resp = model.BuildResponse(result)
	return resp, nil
}

func validationParamsForTxnJoin(request transaction.BranchTxnJoinRequest) (hasError bool, misMatchFields string) {

	if "" == request.Request.ParticipantAddress {
		hasError = true
		misMatchFields = "participantAddress"
	}

	if "" == request.Request.RootXid {
		if hasError {
			misMatchFields = misMatchFields + ", rootXid"
		} else {
			misMatchFields = "rootXid"
		}

		hasError = true
	}

	if "" == request.Request.ParentXid {
		if hasError {
			misMatchFields = misMatchFields + ", parentXid"
		} else {
			misMatchFields = "parentXid"
		}
		hasError = true
	}

	if "" == request.Request.RequestTime {
		if hasError {
			misMatchFields = misMatchFields + ", requestTime"
		} else {
			misMatchFields = FieldRequestTime
		}

		hasError = true
	}
	return
}

func validationParamsForTxnBegin(request transaction.RootTxnBeginRequest) (hasError bool, misMatchFields string) {
	if "" == request.Request.ParticipantAddress {
		hasError = true
		misMatchFields = "participantAddress"
	}

	if "" == request.Request.RequestTime {
		if hasError {
			misMatchFields = misMatchFields + ",requestTime"
		} else {
			misMatchFields = FieldRequestTime
		}

		hasError = true
	}
	return
}

func validationParamsForTxnEnd(request transaction.TxnEndRequest) (hasError bool, misMatchFields string) {
	if "" == request.Request.RootXid {
		hasError = true
		misMatchFields = "rootXid"
	}

	if "" == request.Request.BranchXid {
		if hasError {
			misMatchFields = misMatchFields + ", branchXid"
		} else {
			misMatchFields = "branchXid"
		}

		hasError = true
	}

	if "" == request.Request.RequestTime {
		if hasError {
			misMatchFields = misMatchFields + ", requestTime"
		} else {
			misMatchFields = FieldRequestTime
		}

		hasError = true
	}

	return
}
